package com.osard.test_record.screencapture;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.view.Surface;

import androidx.appcompat.app.AppCompatActivity;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.UUID;


public class ScreenRecordExamples {

    private static final int REQUEST_MEDIA_PROJECTION = 8888;
    private static final String TAG = ScreenRecordExamples.class.getName();
    private AppCompatActivity mActivity;
    private int mWindowWidth;
    private int mWindowHeight;
    private int mScreenDensity;
    private VirtualDisplay mVirtualDisplay;
    private MediaProjectionManager mMediaProjectionManager;
    private MediaProjection mMediaProjection;
    private int mResultCode;
    private Intent mResultData;
    private Bitmap mBitmap;

    // record
    private String mVideoPath;
    private String mVideoName;
    private Surface surface;
    private MediaCodec mMediaCodec;
    private MediaMuxer mMuxer;

    private boolean mIsQuit = true;
    private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
    private boolean mMuxerStarted = false;
    private int mVideoTrackIndex = -1;

    private OnCaptureListener mCaptureListener = null;

    public ScreenRecordExamples(AppCompatActivity activity) {
        this.mActivity = activity;
        createEnvironment();
    }

    public static ScreenRecordExamples newInstance(AppCompatActivity activity) {
        return new ScreenRecordExamples(activity);
    }

    public void setCaptureListener(OnCaptureListener captureListener) {
        this.mCaptureListener = captureListener;
    }

    private void createEnvironment() {
        mScreenDensity = mActivity.getResources().getConfiguration().densityDpi;

        mMediaProjectionManager = (MediaProjectionManager) mActivity.
                getSystemService(Context.MEDIA_PROJECTION_SERVICE);

        mVideoPath = mActivity.getExternalFilesDir("") + File.separator + "record" + File.separator;
    }

    private void screen() {
        mWindowWidth = mActivity.getResources().getDisplayMetrics().widthPixels;
        mWindowHeight = mActivity.getResources().getDisplayMetrics().heightPixels;
    }

    /**
     * @param path 保存的路径
     */
    public void setRecordPath(String path) {
        mVideoPath = path;
    }

    /**
     * @param path      保存的路径
     * @param videoName 保存文件的名字
     */
    public void setRecordPath(String path, String videoName) {
        mVideoPath = path;
        mVideoName = videoName;
    }

    /**
     * @param videoName 保存文件的名字
     */
    public void setRecordVideoName(String videoName) {
        mVideoName = videoName;
    }

    private void setUpVirtualDisplay() {
        mVirtualDisplay = mMediaProjection.createVirtualDisplay("RECORD-SCREEN-" + UUID.randomUUID().toString(),
                mWindowWidth, mWindowHeight, mScreenDensity,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                surface, null, null);
    }

    private void setUpMediaProjection() {
        mMediaProjection = mMediaProjectionManager.getMediaProjection(mResultCode, mResultData);
    }

    // record
    private void configureMedia() {
        //以录制时的屏幕宽高为准
        screen();

        MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, mWindowWidth, mWindowHeight);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 6000000);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 60);
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);
        try {
            mMediaCodec = MediaCodec.createEncoderByType("video/avc");
        } catch (IOException e) {
            e.printStackTrace();
        }
        mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        surface = mMediaCodec.createInputSurface();
        mMediaCodec.start();
    }

    private boolean startScreenCapture() {
        Log.w(TAG, "startScreenCapture");
        if (mResultCode != 0 && mResultData != null) {
            setUpMediaProjection();
            configureMedia();
            setUpVirtualDisplay();
            return true;
        } else {
            Log.w(TAG, "Requesting confirmation");
            // This initiates a prompt dialog for the user to confirm screen projection.
            mActivity.startActivityForResult(
                    mMediaProjectionManager.createScreenCaptureIntent(),
                    REQUEST_MEDIA_PROJECTION);
            return false;
        }
    }

    public void recordStart() {
        if (startScreenCapture()) {
            new Thread() {
                @Override
                public void run() {
                    startRecord();
                }
            }.start();
        }
    }

    private void startRecord() {
        try {
            Log.w(TAG, "Record start");
            if (mCaptureListener != null) {
                new Handler(Looper.getMainLooper()).post(() -> mCaptureListener.onScreenRecordStart());
            }

            if (mVideoPath.charAt(mVideoPath.length() - 1) != '/') {
                mVideoPath = mVideoPath + "/";
            }
            File fileFolder = new File(mVideoPath);
            if (!fileFolder.exists())
                //noinspection ResultOfMethodCallIgnored
                fileFolder.mkdirs();
            if (TextUtils.isEmpty(mVideoName)) {
                mVideoName = System.currentTimeMillis() + ".mp4";
            }
            File file = new File(mVideoPath, mVideoName);
            if (!file.exists()) {
                //noinspection ResultOfMethodCallIgnored
                file.createNewFile();
            }
            mIsQuit = false;
            mMuxer = new MediaMuxer(mVideoPath + mVideoName, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
            recordVirtualDisplay();
        } catch (IOException e) {
            if (mCaptureListener != null) {
                mCaptureListener.onScreenRecordFailed(e.getMessage());
            }
            e.printStackTrace();
        } finally {
            release();
        }
    }

    public void recordStop() {
        Log.w(TAG, "Record stop");
        mIsQuit = true;
        if (mCaptureListener != null) {
            mCaptureListener.onScreenRecordSuccess(mVideoPath + mVideoName);
        }

        if (mCaptureListener != null) {
            mCaptureListener.onScreenRecordStop();
        }
    }

    private void recordVirtualDisplay() {
        while (!mIsQuit) {
            int index = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 10000);
            Log.w(TAG, "dequeue output buffer index=" + index);
            if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                //后续输出格式变化
                resetOutputFormat();
            } else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
                //请求超时
//                Log.w(TAG, "retrieving buffers time out!");
                try {
                    // wait 10ms
                    //noinspection BusyWait
                    Thread.sleep(10);
                } catch (InterruptedException ignore) {
                }
            } else if (index >= 0) {//有效输出
                if (!mMuxerStarted) {
                    throw new IllegalStateException("MediaMuxer dose not call addTrack(format) ");
                }
                encodeToVideoTrack(index);
                mMediaCodec.releaseOutputBuffer(index, false);
            }
        }
    }

    private void resetOutputFormat() {
        // should happen before receiving buffers, and should only happen once
        if (mMuxerStarted) {
            throw new IllegalStateException("output format already changed!");
        }
        MediaFormat newFormat = mMediaCodec.getOutputFormat();

        Log.w(TAG, "output format changed.\n new format: " + newFormat.toString());
        mVideoTrackIndex = mMuxer.addTrack(newFormat);
        mMuxer.start();
        mMuxerStarted = true;
        Log.w(TAG, "started media muxer, videoIndex=" + mVideoTrackIndex);
    }

    private void encodeToVideoTrack(int index) {
        ByteBuffer encodedData = mMediaCodec.getOutputBuffer(index);

        if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {//是编码需要的特定数据，不是媒体数据
            // The codec config data was pulled out and fed to the muxer when we got
            // the INFO_OUTPUT_FORMAT_CHANGED status.
            // Ignore it.
            Log.w(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
            mBufferInfo.size = 0;
        }
        if (mBufferInfo.size == 0) {
            Log.w(TAG, "info.size == 0, drop it.");
            encodedData = null;
        } else {
            Log.w(TAG, "got buffer, info: size=" + mBufferInfo.size
                    + ", presentationTimeUs=" + mBufferInfo.presentationTimeUs
                    + ", offset=" + mBufferInfo.offset);
        }
        if (encodedData != null) {
            encodedData.position(mBufferInfo.offset);
            encodedData.limit(mBufferInfo.offset + mBufferInfo.size);
            mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo);//写入
            Log.w(TAG, "sent " + mBufferInfo.size + " bytes to muxer...");
        }
    }

    private void release() {
        mMuxerStarted = false;

        mResultCode = 0;
        mResultData = null;

        Log.w(TAG, " release() ");

        if (mMediaCodec != null) {
            try {
                mMediaCodec.stop();
                mMediaCodec.release();
            } catch (IllegalStateException ignore) {
            } finally {
                mMediaCodec = null;
            }
        }
        if (mVirtualDisplay != null) {
            try {
                mVirtualDisplay.release();
            } catch (IllegalStateException ignore) {
            } finally {
                mVirtualDisplay = null;
            }
        }
        if (mMuxer != null) {
            try {
                mMuxer.stop();
                mMuxer.release();
            } catch (IllegalStateException ignore) {
            } finally {
                mMuxer = null;
            }
        }
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        Log.w(TAG, "onActivityResult requestCode:" + requestCode + " resultCode:" + resultCode);
        if (requestCode == REQUEST_MEDIA_PROJECTION) {
            if (resultCode != Activity.RESULT_OK) {
                Log.w(TAG, "User cancelled.");
                return;
            }
            mResultCode = resultCode;
            mResultData = data;

            recordStart();
        }
    }

    public void cleanup() {
        if (mBitmap != null) {
            mBitmap.recycle();
            mBitmap = null;
        }

        if (mMediaProjection != null) {
            mMediaProjection.stop();
        }
        Log.w(TAG, " cleanup() ");

    }

    public interface OnCaptureListener {

        void onScreenRecordStart();

        void onScreenRecordStop();

        void onScreenRecordSuccess(String savePath);

        void onScreenRecordFailed(String errorMsg);
    }
}
